package com.snail.auth;

import com.alibaba.fastjson.JSON;
import com.common.ResultCodeEnum;
import com.common.ResultUtil;
import com.common.util.AccessAddressUtil;
import com.common.util.HttpResponseUtil;
import com.common.util.RedisUtil;
import com.snail.auth.entity.SecurityUser;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @autor houyanfeng 2021/5/12
 * @description: token验证的过滤器，拦截请求，并解析token，并将解析的用户信息保存到spring security作用域中
 */
@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    MyUserDetailsService userDetailsService;
    //创建redis模板
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    // 引入 RedisUtil 工具类
    @Autowired
    RedisUtil redisUtil;

    @Value("${jwt.header}")
    private String token_header;

    // 从 application.yml 引入超时时间
    @Value("${token.expirationSeconds}")
    private int expirationSeconds;

    // 从 application.yml 引入有效时间
    @Value("${token.validTime}")
    private int validTime;
    @Value("${token.salt}")
    private String salt;

    @Autowired
    private HttpResponseUtil httpResponseUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String url = request.getRequestURI();
        if(url.endsWith(".css")||url.endsWith(".js")||url.endsWith(".jpg")
                ||url.endsWith(".gif")||url.endsWith(".png")||url.endsWith("/index.jsp")
                ||url.endsWith(".html")) {
            filterChain.doFilter(request, response);
            return;
        }
        // 获取请求头的权限
        String authToken = request.getHeader(token_header);
        //获取请求的ip地址
        String currentIp = AccessAddressUtil.getIpAddress(request);

        if (authToken != null && !"".equals(authToken.trim())) {//说明非登陆进入
            //判断 token 是否在黑名单里面
            if (redisUtil.isBlackList(authToken)) {
                log.info("该token在黑名单之中，拒绝访问（token:{}）", authToken);
                httpResponseUtil.write(response, ResultCodeEnum.LOGIN_BLACKLIST);
                return;
            }
            /* 判断token是否过期，从redis中读取有效时间（比如七天登录有效），未过期的话生成新的token存储redis并返回客户端）*/
            if (redisUtil.hasKey(authToken)) {
                try {
                    Claims claims = JwtTokenUtil.getTokenClaim(authToken, salt);
                    String username = claims.getSubject();

                    String ip = redisUtil.getHash(authToken, "ip").toString();
                    /* 加入对ip的验证，如果ip不正确，进入黑名单
                    if (!StringUtil.equals(ip, currentIp)) {//地址不正确
                        log.info("用户：{}的ip地址变动，该token作废，列入黑名单", username);
                        redisUtil.addBlackList(authToken);
                        //删除旧的token保存的redis
                        redisUtil.deleteKey(authToken);
                        httpResponseUtil.write(response, ResultCodeEnum.LOGIN_BLACKLIST);
                        return;
                    }
                     */
                    String expirationTime = redisUtil.getHash(authToken, "expirationTime").toString();
                    //判断如果token过了存活期，距离存活期还剩10分钟时即刷新token
                    if (JwtTokenUtil.isOverdue(expirationTime)) {
                        Map<String, Object> oldRedisMap = (Map<String, Object>) redisUtil.getHash(authToken);
                        //这个token已作废，加入黑名单。生成新的token返回给客户端
                        log.info("{}已作废，加入黑名单", authToken);
                        redisUtil.addBlackList(authToken);
                        //生成新的token
                        Map<String, Object> dataMap = new HashMap<>();
                        String newToken = JwtTokenUtil.generateToken(username, validTime, dataMap, salt);
                        //新的token保存到redis中
                        redisUtil.setTokenRefresh(newToken, oldRedisMap.get("userId").toString(), username, ip, oldRedisMap.get("auths").toString());
                        //删除旧的token保存的redis
                        redisUtil.deleteKey(authToken);
                        log.info("redis已删除旧token：{},新token：{}已更新redis", authToken, newToken);
                        response.setHeader("Access-Control-Expose-Headers", token_header);
                        response.setHeader(token_header, newToken);
                    } else {
                        //没有过期，仍返回原token,刷新有效期
                        response.setHeader("Access-Control-Expose-Headers", token_header);
                        response.setHeader(token_header, authToken);
                    }
                    UserDetails securityUser = userDetailsService.loadUserByUsername(username);
                    //根据用户拥有的权限列表生成授权token
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(securityUser,
                            null, securityUser.getAuthorities());
                    //将user信息存储至上下文中，可随意获取
                    SecurityContextHolder.getContext().setAuthentication(authentication);

                } catch (ExpiredJwtException e) {//getTokenClaim发现token过有效期后报此错误。
                    e.printStackTrace();
                    log.info("token已失效，请重新登录");
                    httpResponseUtil.write(response, ResultCodeEnum.LOGIN_OVERDUE);
                    return;
                }
            } else {
                httpResponseUtil.write(response, ResultCodeEnum.LOGIN_OVERDUE);
                return;
            }
            //继续过滤器链的请求
            filterChain.doFilter(request, response);
            return;
        }else if(request.getRequestURI().endsWith("/login")){
            filterChain.doFilter(request, response);
            return;
        }
        filterChain.doFilter(request, response);
//        response.setCharacterEncoding("UTF-8");
//        response.getWriter().write(JSON.toJSONString(ResultUtil.result(ResultCodeEnum.LOGIN_OVERDUE)));
    }

}
